home *** CD-ROM | disk | FTP | other *** search
/ Chip 2004 December / 2004-12 CHIP.iso / Internet / NVU 0.50 for Windows / nvu-0.50-win32-installer-full.exe / {app} / components / nsHelperAppDlg.js < prev    next >
Encoding:
JavaScript  |  2004-08-04  |  31.6 KB  |  834 lines

  1. /*
  2. */
  3.  
  4. /* This file implements the nsIHelperAppLauncherDialog interface.
  5.  *
  6.  * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
  7.  * comprised of:
  8.  *   - a JS constructor function
  9.  *   - a prototype providing all the interface methods and implementation stuff
  10.  *
  11.  * In addition, this file implements an nsIModule object that registers the
  12.  * nsUnknownContentTypeDialog component.
  13.  */
  14.  
  15.  
  16. /* ctor
  17.  */
  18. function nsUnknownContentTypeDialog() {
  19.     // Initialize data properties.
  20.     this.mLauncher = null;
  21.     this.mContext  = null;
  22.     this.mSourcePath = null;
  23.     this.chosenApp = null;
  24.     this.givenDefaultApp = false;
  25.     this.updateSelf = true;
  26.     this.mTitle    = "";
  27. }
  28.  
  29. nsUnknownContentTypeDialog.prototype = {
  30.     nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
  31.  
  32.     // This "class" supports nsIHelperAppLauncherDialog, and nsISupports.
  33.     QueryInterface: function (iid) {
  34.         if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
  35.             !iid.equals(Components.interfaces.nsISupports)) {
  36.             throw Components.results.NS_ERROR_NO_INTERFACE;
  37.         }
  38.         return this;
  39.     },
  40.  
  41.     // ---------- nsIHelperAppLauncherDialog methods ----------
  42.  
  43.     // show: Open XUL dialog using window watcher.  Since the dialog is not
  44.     //       modal, it needs to be a top level window and the way to open
  45.     //       one of those is via that route).
  46.     show: function(aLauncher, aContext)  {  
  47.       this.mLauncher = aLauncher;
  48.       this.mContext  = aContext;
  49.       // Display the dialog using the Window Watcher interface.
  50.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  51.                 .getService(Components.interfaces.nsIWindowWatcher);
  52.       this.mDialog = ww.openWindow(null, // no parent
  53.                                    "chrome://mozapps/content/downloads/unknownContentType.xul",
  54.                                    null,
  55.                                    "chrome,centerscreen,titlebar,dialog=yes",
  56.                                    null);
  57.       // Hook this object to the dialog.
  58.       this.mDialog.dialog = this;
  59.       
  60.       // Hook up utility functions. 
  61.       // XXXben these lines can disappear if we can get XULPP to run on stuff not 
  62.       //        thus referenced in jar.mns. 
  63.       this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
  64.       
  65.       // Watch for error notifications.
  66.       this.progressListener.helperAppDlg = this;
  67.       this.mLauncher.setWebProgressListener(this.progressListener);
  68.     },
  69.  
  70.     // promptForSaveToFile:  Display file picker dialog and return selected file.
  71.     //                       This is called by the External Helper App Service
  72.     //                       after the ucth dialog calls |saveToDisk| with a null
  73.     //                       target filename (no target, therefore user must pick).
  74.     //
  75.     //                       Alternatively, if the user has selected to have all
  76.     //                       files download to a specific location, return that
  77.     //                       location and don't ask via the dialog. 
  78.     //
  79.     // Note - this function is called without a dialog, so it cannot access any part
  80.     // of the dialog XUL as other functions on this object do. 
  81.     promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension) {
  82.       var result = "";
  83.       
  84.       this.mLauncher = aLauncher;
  85.  
  86.       // If the user is always downloading to the same location, the default download
  87.       // folder is stored in preferences. If a value is found stored, use that 
  88.       // automatically and don't ask via a dialog. 
  89.       const kDownloadFolderPref = "browser.download.defaultFolder";
  90.       var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  91.       try {
  92.         result = prefs.getComplexValue(kDownloadFolderPref, Components.interfaces.nsILocalFile);
  93.         result = this.validateLeafName(result, aDefaultFile, aSuggestedFileExtension);
  94.       }
  95.       catch (e) { 
  96.         // If we get here, it's because we have a new profile and the user has never configured download
  97.         // options, so "browser.download.defaultFolder" is not set yet. If the default is autodownload, 
  98.         // we need to discover the default save location. 
  99.         var autodownload = prefs.getBoolPref("browser.download.useDownloadDir");
  100.         if (autodownload) {
  101.           // XXXben we should really have a way of using XULPP on JS component files. 
  102.           // This code is very similar to that in pref-downloads.js, except the platform
  103.           // checks are done at runtime. ugh. 
  104.           function getSpecialFolderKey(aFolderType) 
  105.           {
  106.             return aFolderType == "Desktop" ? "DeskV" : "Pers";
  107.           }
  108.           
  109.           function getDownloadsFolder(aFolder)
  110.           {
  111.             var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties);
  112.  
  113.             var dir = fileLocator.get(getSpecialFolderKey(aFolder), Components.interfaces.nsILocalFile);
  114.             
  115.             var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  116.             bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
  117.  
  118.             var description = bundle.GetStringFromName("myDownloads");
  119.             if (aFolder != "Desktop")
  120.               dir.append(description);
  121.               
  122.             return dir;
  123.           }
  124.  
  125.           var defaultFolder = null;
  126.           switch (prefs.getIntPref("browser.download.folderList")) {
  127.           case 0:
  128.             defaultFolder = getDownloadsFolder("Desktop")
  129.             break;
  130.           case 1:
  131.             defaultFolder = getDownloadsFolder("Downloads");
  132.             break;
  133.           case 2:
  134.             defaultFolder = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
  135.             break;
  136.           }
  137.           
  138.           // While we're here, set the pref too so that we don't keep coming back into this less efficient
  139.           // code block. 
  140.           prefs.setComplexValue("browser.download.defaultFolder", Components.interfaces.nsILocalFile, defaultFolder);
  141.           
  142.           result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
  143.         }
  144.       }
  145.       
  146.       if (!result) {
  147.         // Use file picker to show dialog.
  148.         var nsIFilePicker = Components.interfaces.nsIFilePicker;
  149.         var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  150.  
  151.         var bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
  152.         bundle = bundle.createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
  153.  
  154.         var windowTitle = bundle.GetStringFromName("saveDialogTitle");
  155.         var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal);
  156.         picker.init(parent, windowTitle, nsIFilePicker.modeSave);
  157.         picker.defaultString = aDefaultFile;
  158.  
  159.         if (aSuggestedFileExtension) {
  160.           // aSuggestedFileExtension includes the period, so strip it
  161.           picker.defaultExtension = aSuggestedFileExtension.substring(1);
  162.         } 
  163.         else {
  164.           try {
  165.             picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
  166.           } 
  167.           catch (ex) { }
  168.         }
  169.  
  170.         var wildCardExtension = "*";
  171.         if (aSuggestedFileExtension) {
  172.           wildCardExtension += aSuggestedFileExtension;
  173.           picker.appendFilter(this.mLauncher.MIMEInfo.Description, wildCardExtension);
  174.         }
  175.  
  176.         picker.appendFilters( nsIFilePicker.filterAll );
  177.  
  178.         // Pull in the user's preferences and get the default download directory.
  179.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  180.         try {
  181.           var startDir = prefs.getComplexValue("browser.download.dir", Components.interfaces.nsILocalFile);
  182.           if (startDir.exists()) {
  183.             picker.displayDirectory = startDir;
  184.           }
  185.         } 
  186.         catch(exception) { }
  187.  
  188.         var dlgResult = picker.show();
  189.  
  190.         if (dlgResult == nsIFilePicker.returnCancel) {
  191.           // null result means user cancelled.
  192.           return null;
  193.         }
  194.  
  195.  
  196.         // Be sure to save the directory the user chose through the Save As... 
  197.         // dialog  as the new browser.download.dir
  198.         result = picker.file;
  199.  
  200.         if (result) {
  201.           var newDir = result.parent;
  202.           prefs.setComplexValue("browser.download.dir", Components.interfaces.nsILocalFile, newDir);
  203.         }
  204.       }
  205.       return result;
  206.     },
  207.     
  208.     validateLeafName: function (aLocalFile, aLeafName, aFileExt)
  209.     {
  210.       if (aLeafName == "")
  211.         aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
  212.       aLocalFile.append(aLeafName);
  213.       
  214.       // Since we're automatically downloading, we don't get the file picker's 
  215.       // logic to check for existing files, so we need to do that here.
  216.       //
  217.       // Note - this code is identical to that in 
  218.       //   browser/base/content/contentAreaUtils.js. 
  219.       // If you are updating this code, update that code too! We can't share code
  220.       // here since this is called in a js component. 
  221.       while (aLocalFile.exists()) {
  222.         var parts = /.+-(\d+)(\..*)?$/.exec(aLocalFile.leafName);
  223.         if (parts) {
  224.           aLocalFile.leafName = aLocalFile.leafName.replace(/((\d+)\.)/, 
  225.                                                             function (str, p1, part, s) { 
  226.                                                               return (parseInt(part) + 1) + "."; 
  227.                                                             });
  228.         }
  229.         else {
  230.           aLocalFile.leafName = aLocalFile.leafName.replace(/\./, "-1$&");
  231.         }
  232.       }
  233.       
  234.       return aLocalFile;
  235.     },
  236.     
  237.     // ---------- implementation methods ----------
  238.  
  239.     // Web progress listener so we can detect errors while mLauncher is
  240.     // streaming the data to a temporary file.
  241.     progressListener: {
  242.         // Implementation properties.
  243.         helperAppDlg: null,
  244.  
  245.         // nsIWebProgressListener methods.
  246.         // Look for error notifications and display alert to user.
  247.         onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
  248.             if ( aStatus != Components.results.NS_OK ) {
  249.                 // Get prompt service.
  250.                 var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
  251.                                    .getService( Components.interfaces.nsIPromptService );
  252.                 // Display error alert (using text supplied by back-end).
  253.                 prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
  254.  
  255.                 // Close the dialog.
  256.                 this.helperAppDlg.onCancel();
  257.                 if ( this.helperAppDlg.mDialog ) {
  258.                     this.helperAppDlg.mDialog.close();
  259.                 }
  260.             }
  261.         },
  262.  
  263.         // Ignore onProgressChange, onStateChange, onLocationChange, and onSecurityChange notifications.
  264.         onProgressChange: function( aWebProgress,
  265.                                     aRequest,
  266.                                     aCurSelfProgress,
  267.                                     aMaxSelfProgress,
  268.                                     aCurTotalProgress,
  269.                                     aMaxTotalProgress ) {
  270.         },
  271.  
  272.         onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
  273.         },
  274.  
  275.         onLocationChange: function( aWebProgress, aRequest, aLocation ) {
  276.         },
  277.  
  278.         onSecurityChange: function( aWebProgress, aRequest, state ) {
  279.         }
  280.     },
  281.  
  282.     // initDialog:  Fill various dialog fields with initial content.
  283.     initDialog : function() {
  284.       // Put file name in window title.
  285.       var win   = this.dialogElement( "unknownContentType" );
  286.       var suggestedFileName = this.mLauncher.suggestedFileName;
  287.  
  288.       // Some URIs do not implement nsIURL, so we can't just QI.
  289.       var url   = this.mLauncher.source;
  290.       var fname = "";
  291.       this.mSourcePath = url.prePath;
  292.       try {
  293.           url = url.QueryInterface( Components.interfaces.nsIURL );
  294.           // A url, use file name from it.
  295.           fname = url.fileName;
  296.           this.mSourcePath += url.directory;
  297.       } catch (ex) {
  298.           // A generic uri, use path.
  299.           fname = url.path;
  300.           this.mSourcePath += url.path;
  301.       }
  302.  
  303.       if (suggestedFileName)
  304.         fname = suggestedFileName;
  305.         
  306.  
  307.       this.mTitle = this.dialogElement("strings").getFormattedString("title", [fname]);
  308.       win.setAttribute( "title", this.mTitle );
  309.  
  310.       // Put content type, filename and location into intro.
  311.       this.initIntro(url, fname);
  312.  
  313.       var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
  314.       this.dialogElement("contentTypeImage").setAttribute("src", iconString);
  315.  
  316.       this.initAppAndSaveToDiskValues();
  317.  
  318.       // Initialize "always ask me" box. This should always be disabled
  319.       // and set to true for the ambiguous type application/octet-stream.
  320.       // We don't also check for application/x-msdownload here since we
  321.       // want users to be able to autodownload .exe files. 
  322.       var rememberChoice = this.dialogElement("rememberChoice");
  323.  
  324.       if (this.mLauncher.MIMEInfo.MIMEType == "application/octet-stream") {
  325.         rememberChoice.checked = false;
  326.         rememberChoice.disabled = true;
  327.       }
  328.       else {
  329.         rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  330.       }
  331.       this.toggleRememberChoice(rememberChoice);
  332.  
  333.  
  334.       // XXXben - menulist won't init properly, hack. 
  335.       var openHandler = this.dialogElement("openHandler");
  336.       openHandler.parentNode.removeChild(openHandler);
  337.       var openParent = this.dialogElement("open").parentNode;
  338.       openParent.appendChild(openHandler);
  339.  
  340.       this.mDialog.setTimeout("dialog.postShowCallback()", 0);
  341.     },
  342.     
  343.     postShowCallback: function () {
  344.       this.mDialog.sizeToContent();
  345.  
  346.       // Set initial focus
  347.       this.dialogElement("mode").focus();
  348.     },
  349.  
  350.     // initIntro:
  351.     initIntro: function(url, filename) {
  352.         this.dialogElement( "location" ).value = filename;
  353.  
  354.         // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
  355.         // url...
  356.         var pathString = this.mSourcePath;
  357.         try 
  358.         {
  359.           var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
  360.           if (fileURL)
  361.           {
  362.             var fileObject = fileURL.file;
  363.             if (fileObject)
  364.             {
  365.               var parentObject = fileObject.parent;
  366.               if (parentObject)
  367.               {
  368.                 pathString = parentObject.path;
  369.               }
  370.             }
  371.           }
  372.         } catch(ex) {}
  373.  
  374.         // Set the location text, which is separate from the intro text so it can be cropped
  375.         var location = this.dialogElement( "source" );
  376.         location.value = pathString;
  377.         
  378.         // Show the type of file. 
  379.         var type = this.dialogElement("type");
  380.         var mimeInfo = this.mLauncher.MIMEInfo;
  381.         
  382.         // 1. Try to use the pretty description of the type, if one is available.
  383.         var typeString = mimeInfo.Description;
  384.         
  385.         if (typeString == "") {
  386.           // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
  387.           var primaryExtension = "";
  388.           try {
  389.             primaryExtension = mimeInfo.primaryExtension;
  390.           }
  391.           catch (ex) {
  392.           }
  393.           if (primaryExtension != "")
  394.             typeString = primaryExtension.toUpperCase() + " file";
  395.           // 3. If we can't even do that, just give up and show the MIME type. 
  396.           else
  397.             typeString = mimeInfo.MIMEType;
  398.         }
  399.         
  400.         type.value = typeString;
  401.     },
  402.  
  403.     // Returns true if opening the default application makes sense.
  404.     openWithDefaultOK: function() {
  405.         var result;
  406.  
  407.         // The checking is different on Windows...
  408.         // Windows presents some special cases.
  409.         // We need to prevent use of "system default" when the file is
  410.         // executable (so the user doesn't launch nasty programs downloaded
  411.         // from the web), and, enable use of "system default" if it isn't
  412.         // executable (because we will prompt the user for the default app
  413.         // in that case).
  414.         
  415.         // Need to get temporary file and check for executable-ness.
  416.         var tmpFile = this.mLauncher.targetFile;
  417.         
  418.         //  Default is Ok if the file isn't executable (and vice-versa).
  419.         return !tmpFile.isExecutable();
  420.     },
  421.     
  422.     // Set "default" application description field.
  423.     initDefaultApp: function() {
  424.       // Use description, if we can get one.
  425.       var desc = this.mLauncher.MIMEInfo.defaultDescription;
  426.       if (desc) {
  427.         var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
  428.         this.dialogElement("defaultHandler").label = defaultApp;
  429.       }
  430.     },
  431.  
  432.     // getPath:
  433.     getPath: function (aFile) {
  434.       return aFile.path;
  435.     },
  436.  
  437.     // initAppAndSaveToDiskValues:
  438.     initAppAndSaveToDiskValues: function() {
  439.       var modeGroup = this.dialogElement("mode");
  440.  
  441.       // We don't let users open .exe files or random binary data directly 
  442.       // from the browser at the moment because of security concerns. 
  443.       var openWithDefaultOK = this.openWithDefaultOK();
  444.       var mimeType = this.mLauncher.MIMEInfo.MIMEType;
  445.       if ((mimeType == "application/octet-stream" ||
  446.            mimeType == "application/x-msdownload") && !openWithDefaultOK) {
  447.         this.dialogElement("open").disabled = true;
  448.         var openHandler = this.dialogElement("openHandler");
  449.         openHandler.disabled = true;
  450.         openHandler.label = "";
  451.         modeGroup.selectedItem = this.dialogElement("save");
  452.         return;
  453.       }
  454.     
  455.       // Fill in helper app info, if there is any.
  456.       this.chosenApp = this.mLauncher.MIMEInfo.preferredApplicationHandler;
  457.       // Initialize "default application" field.
  458.       this.initDefaultApp();
  459.  
  460.       var otherHandler = this.dialogElement("otherHandler");
  461.               
  462.       // Fill application name textbox.
  463.       if (this.chosenApp && this.chosenApp.path) {
  464.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  465.         otherHandler.label = this.chosenApp.leafName;
  466.         otherHandler.hidden = false;
  467.       }
  468.  
  469.       var useDefault = this.dialogElement("useSystemDefault");
  470.       var openHandler = this.dialogElement("openHandler");
  471.       openHandler.selectedIndex = 0;
  472.  
  473.       if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
  474.         // Open (using system default).
  475.         modeGroup.selectedItem = this.dialogElement("open");
  476.       } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
  477.         // Open with given helper app.
  478.         modeGroup.selectedItem = this.dialogElement("open");
  479.         openHandler.selectedIndex = 1;
  480.       } else {
  481.         // Save to disk.
  482.         modeGroup.selectedItem = this.dialogElement("save");
  483.       }
  484.       
  485.       // If we don't have a "default app" then disable that choice.
  486.       if (!openWithDefaultOK) {
  487.         var useDefault = this.dialogElement("defaultHandler");
  488.         var isSelected = useDefault.selected;
  489.         
  490.         // Disable that choice.
  491.         useDefault.hidden = true;
  492.         // If that's the default, then switch to "save to disk."
  493.         if (isSelected) {
  494.           openHandler.selectedIndex = 1;
  495.           modeGroup.selectedItem = this.dialogElement("save");
  496.         }
  497.       }
  498.       
  499.       // otherHandler is always disabled on Mac
  500.       otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
  501.       this.updateOKButton();
  502.     },
  503.  
  504.     // Returns the user-selected application
  505.     helperAppChoice: function() {
  506.       return this.chosenApp;
  507.     },
  508.     
  509.     get saveToDisk() {
  510.       return this.dialogElement("save").selected;
  511.     },
  512.     
  513.     get useOtherHandler() {
  514.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
  515.     },
  516.     
  517.     get useSystemDefault() {
  518.       return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
  519.     },
  520.     
  521.     toggleRememberChoice: function (aCheckbox) {
  522.         this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
  523.         this.mDialog.sizeToContent();
  524.     },
  525.     
  526.     openHandlerCommand: function () {
  527.       if (this.dialogElement("openHandler").selectedItem.id == "choose")
  528.         this.chooseApp();
  529.     },
  530.  
  531.     updateOKButton: function() {
  532.       var ok = false;
  533.       if (this.dialogElement("save").selected) {
  534.         // This is always OK.
  535.         ok = true;
  536.       } 
  537.       else if (this.dialogElement("open").selected) {
  538.         switch (this.dialogElement("openHandler").selectedIndex) {
  539.         case 0:
  540.           // No app need be specified in this case.
  541.           ok = true;
  542.           break;
  543.         case 1:
  544.           // only enable the OK button if we have a default app to use or if 
  545.           // the user chose an app....
  546.           ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
  547.         break;
  548.         }
  549.       }
  550.  
  551.       // Enable Ok button if ok to press.
  552.       this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
  553.     },
  554.     
  555.     // Returns true iff the user-specified helper app has been modified.
  556.     appChanged: function() {
  557.       return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
  558.     },
  559.  
  560.     updateMIMEInfo: function() {
  561.       var needUpdate = false;
  562.       // If current selection differs from what's in the mime info object,
  563.       // then we need to update.
  564.       if (this.saveToDisk) {
  565.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
  566.         if (needUpdate)
  567.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
  568.       } 
  569.       else if (this.useSystemDefault) {
  570.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
  571.         if (needUpdate)
  572.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
  573.       } 
  574.       else {
  575.         // For "open with", we need to check both preferred action and whether the user chose
  576.         // a new app.
  577.         needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
  578.         if (needUpdate) {
  579.           this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
  580.           // App may have changed - Update application and description
  581.           var app = this.helperAppChoice();
  582.           this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
  583.           this.mLauncher.MIMEInfo.applicationDescription = "";
  584.         }
  585.       }
  586.       // We will also need to update if the "always ask" flag has changed.
  587.       needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
  588.  
  589.       // One last special case: If the input "always ask" flag was false, then we always
  590.       // update.  In that case we are displaying the helper app dialog for the first
  591.       // time for this mime type and we need to store the user's action in the mimeTypes.rdf
  592.       // data source (whether that action has changed or not; if it didn't change, then we need
  593.       // to store the "always ask" flag so the helper app dialog will or won't display
  594.       // next time, per the user's selection).
  595.       needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
  596.  
  597.       // Make sure mime info has updated setting for the "always ask" flag.
  598.       this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
  599.  
  600.       return needUpdate;        
  601.     },
  602.     
  603.     // See if the user changed things, and if so, update the
  604.     // mimeTypes.rdf entry for this mime type.
  605.     updateHelperAppPref: function() {
  606.       var ha = new this.mDialog.HelperApps();
  607.       ha.updateTypeInfo(this.mLauncher.MIMEInfo);
  608.     },
  609.     
  610.     // onOK:
  611.     onOK: function() {
  612.       // Verify typed app path, if necessary.
  613.       if (this.useOtherHandler) {
  614.         var helperApp = this.helperAppChoice();
  615.         if (!helperApp || !helperApp.exists()) {
  616.           // Show alert and try again.        
  617.           var bundle = this.dialogElement("strings");                    
  618.           var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").path]);
  619.           var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
  620.           svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
  621.  
  622.           // Disable the OK button.
  623.           this.mDialog.document.documentElement.getButton("accept").disabled = true;
  624.           this.dialogElement("mode").focus();          
  625.  
  626.           // Clear chosen application.
  627.           this.chosenApp = null;
  628.  
  629.           // Leave dialog up.
  630.           return false;
  631.         }
  632.       }
  633.         
  634.       // Remove our web progress listener (a progress dialog will be
  635.       // taking over).
  636.       this.mLauncher.setWebProgressListener(null);
  637.       
  638.       // saveToDisk and launchWithApplication can return errors in 
  639.       // certain circumstances (e.g. The user clicks cancel in the
  640.       // "Save to Disk" dialog. In those cases, we don't want to
  641.       // update the helper application preferences in the RDF file.
  642.       try {
  643.         var needUpdate = this.updateMIMEInfo();
  644.         
  645.         if (this.dialogElement("save").selected) {
  646.           // If we're using a default download location, create a path
  647.           // for the file to be saved to to pass to |saveToDisk| - otherwise
  648.           // we must ask the user to pick a save name.
  649.  
  650.           var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
  651.           var targetFile = null;
  652.           try {
  653.             targetFile = prefs.getComplexValue("browser.download.defaultFolder", 
  654.                                                Components.interfaces.nsILocalFile);
  655.             var leafName = this.dialogElement("location").value;                                               
  656.             // Ensure that we don't overwrite any existing files here. 
  657.             targetFile = this.validateLeafName(targetFile, leafName, null);
  658.           }
  659.           catch(e) {}
  660.           
  661.           this.mLauncher.saveToDisk(targetFile, false);
  662.         }
  663.         else
  664.           this.mLauncher.launchWithApplication(null, false);
  665.  
  666.         // Update user pref for this mime type (if necessary). We do not
  667.         // store anything in the mime type preferences for the ambiguous
  668.         // type application/octet-stream. We do NOT do this for 
  669.         // application/x-msdownload since we want users to be able to 
  670.         // autodownload these to disk. 
  671.         if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
  672.           this.updateHelperAppPref();
  673.       } catch(e) { }
  674.  
  675.       // Unhook dialog from this object.
  676.       this.mDialog.dialog = null;
  677.  
  678.       // Close up dialog by returning true.
  679.       return true;
  680.     },
  681.  
  682.     // onCancel:
  683.     onCancel: function() {
  684.       // Remove our web progress listener.
  685.       this.mLauncher.setWebProgressListener(null);
  686.  
  687.       // Cancel app launcher.
  688.       try {
  689.         this.mLauncher.Cancel();
  690.       } catch(exception) {
  691.       }
  692.  
  693.       // Unhook dialog from this object.
  694.       this.mDialog.dialog = null;
  695.  
  696.       // Close up dialog by returning true.
  697.       return true;
  698.     },
  699.  
  700.     // dialogElement:  Convenience. 
  701.     dialogElement: function(id) {
  702.       return this.mDialog.document.getElementById(id);
  703.     },
  704.  
  705.     // chooseApp:  Open file picker and prompt user for application.
  706.     chooseApp: function() {
  707.       var nsIFilePicker = Components.interfaces.nsIFilePicker;
  708.       var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  709.       fp.init(this.mDialog,
  710.               this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
  711.               nsIFilePicker.modeOpen);
  712.  
  713.       fp.appendFilters(nsIFilePicker.filterApps);
  714.  
  715.       if (fp.show() == nsIFilePicker.returnOK && fp.file) {
  716.         // Remember the file they chose to run.
  717.         this.chosenApp = fp.file;
  718.         // Update dialog.
  719.         var otherHandler = this.dialogElement("otherHandler");
  720.         otherHandler.removeAttribute("hidden");
  721.         otherHandler.setAttribute("path", this.getPath(this.chosenApp));
  722.         otherHandler.label = this.chosenApp.leafName;
  723.         this.dialogElement("openHandler").selectedIndex = 1;
  724.         
  725.         this.dialogElement("mode").selectedItem = this.dialogElement("open");
  726.       }
  727.     },
  728.  
  729.     // Turn this on to get debugging messages.
  730.     debug: false,
  731.  
  732.     // Dump text (if debug is on).
  733.     dump: function( text ) {
  734.         if ( this.debug ) {
  735.             dump( text ); 
  736.         }
  737.     },
  738.  
  739.     // dumpInfo:
  740.     doDebug: function() {
  741.         const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
  742.         // Open new progress dialog.
  743.         var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
  744.                          .createInstance( nsIProgressDialog );
  745.         // Show it.
  746.         progress.open( this.mDialog );
  747.     },
  748.  
  749.     // dumpObj:
  750.     dumpObj: function( spec ) {
  751.          var val = "<undefined>";
  752.          try {
  753.              val = eval( "this."+spec ).toString();
  754.          } catch( exception ) {
  755.          }
  756.          this.dump( spec + "=" + val + "\n" );
  757.     },
  758.  
  759.     // dumpObjectProperties
  760.     dumpObjectProperties: function( desc, obj ) {
  761.          for( prop in obj ) {
  762.              this.dump( desc + "." + prop + "=" );
  763.              var val = "<undefined>";
  764.              try {
  765.                  val = obj[ prop ];
  766.              } catch ( exception ) {
  767.              }
  768.              this.dump( val + "\n" );
  769.          }
  770.     }
  771. }
  772.  
  773. // This Component's module implementation.  All the code below is used to get this
  774. // component registered and accessible via XPCOM.
  775. var module = {
  776.     firstTime: true,
  777.  
  778.     // registerSelf: Register this component.
  779.     registerSelf: function (compMgr, fileSpec, location, type) {
  780.         if (this.firstTime) {
  781.             this.firstTime = false;
  782.             throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
  783.         }
  784.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  785.  
  786.         compMgr.registerFactoryLocation( this.cid,
  787.                                          "Unknown Content Type Dialog",
  788.                                          this.contractId,
  789.                                          fileSpec,
  790.                                          location,
  791.                                          type );
  792.     },
  793.  
  794.     // getClassObject: Return this component's factory object.
  795.     getClassObject: function (compMgr, cid, iid) {
  796.         if (!cid.equals(this.cid)) {
  797.             throw Components.results.NS_ERROR_NO_INTERFACE;
  798.         }
  799.  
  800.         if (!iid.equals(Components.interfaces.nsIFactory)) {
  801.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  802.         }
  803.  
  804.         return this.factory;
  805.     },
  806.  
  807.     /* CID for this class */
  808.     cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
  809.  
  810.     /* Contract ID for this class */
  811.     contractId: "@mozilla.org/helperapplauncherdialog;1",
  812.  
  813.     /* factory object */
  814.     factory: {
  815.         // createInstance: Return a new nsProgressDialog object.
  816.         createInstance: function (outer, iid) {
  817.             if (outer != null)
  818.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  819.  
  820.             return (new nsUnknownContentTypeDialog()).QueryInterface(iid);
  821.         }
  822.     },
  823.  
  824.     // canUnload: n/a (returns true)
  825.     canUnload: function(compMgr) {
  826.         return true;
  827.     }
  828. };
  829.  
  830. // NSGetModule: Return the nsIModule object.
  831. function NSGetModule(compMgr, fileSpec) {
  832.     return module;
  833. }
  834.